iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
IT Operation

系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server系列 第 5

Day 5|PowerShell 變數、陣列與迴圈:一次處理多台 Server 的第一步

  • 分享至 

  • xImage
  •  

前面幾天,我們做的事情大多還是針對「目前這一台電腦」。

例如:

Get-Service

或是:

Get-Process

到了 Day 4,我們甚至已經可以把結果整理成 CSV:

取得資料

篩選

整理欄位

Export-Csv

但實際做系統維運時,很少永遠只有一台 Server。

可能今天需要檢查:

SERVER01
SERVER02
SERVER03
SERVER04
SERVER05

如果還是一台一台登入、一台一台執行相同指令,自動化帶來的幫助其實還很有限。

所以 Day 5 我想往前走一步:

讓 PowerShell 開始知道「我要處理的不只一台機器」。

今天會碰到三個很重要的觀念:

$Variable
@()
foreach

也就是:

變數、陣列、迴圈。

最後會做一個簡單的 Server 連線檢查,把多台 Server 的結果整理成 CSV。

先從變數開始

其實前一天我們已經偷偷用過變數了。

例如:

$ReportFolder = "C:\Temp"

這裡:

$ReportFolder

就是一個變數。

可以把變數理解成:

幫一個資料取名字,之後可以重複使用。

例如:

$ServerName = "SERVER01"

接著輸入:

$ServerName

結果:

SERVER01

如果之後我們需要使用這個 Server Name,就不用一直重新輸入:

"SERVER01"

而是直接:

$ServerName
為什麼 Script 要使用變數?

假設我寫:

Export-Csv "C:\Temp\ServerReport.csv"

只有一行時,好像沒有什麼問題。

但如果一支 Script 裡面有十幾個地方都使用:

C:\Temp

有一天主管突然說:

報表以後全部放到 D:\Reports。

如果路徑全部寫死,就要一行一行修改。

如果一開始寫:

$ReportFolder = "C:\Temp"

後面全部使用:

$ReportFolder

那就只需要改:

$ReportFolder = "D:\Reports"

這也是為什麼寫 Script 時,我通常會把容易變動的設定放在前面。

例如:

$ReportFolder = "C:\Temp"
$ServerName = "SERVER01"
$Date = Get-Date -Format "yyyyMMdd"

後面程式直接使用這些變數。

變數不只能放文字

PowerShell 變數可以存很多不同東西。

例如文字:

$ServerName = "SERVER01"

數字:

$DiskThreshold = 80

日期:

$CheckTime = Get-Date

甚至可以直接把 PowerShell 指令的結果放進變數。

例如:

$Services = Get-Service

接著:

$Services

就會顯示 Service 資料。

甚至可以:

$Services |
Where-Object Status -eq "Running"

所以變數不只是拿來存一個字。

它也可以暫時保存我們查詢到的一整批 Object。

一台 Server 很簡單,那五台呢?

如果只有一台:

$Server = "SERVER01"

但現在有:

SERVER01
SERVER02
SERVER03
SERVER04
SERVER05

當然可以寫:

$Server1 = "SERVER01"
$Server2 = "SERVER02"
$Server3 = "SERVER03"
$Server4 = "SERVER04"
$Server5 = "SERVER05"

但這樣很快就會失控。

如果今天不是 5 台,而是:

50 台
100 台

總不能一直:

$Server1
$Server2
$Server3
...
$Server100

這時候就需要:

Array,也就是陣列。

建立第一個 Array

PowerShell 可以這樣建立一個 Server 清單:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
"SERVER04"
"SERVER05"
)

現在:

$Servers

會顯示:

SERVER01
SERVER02
SERVER03
SERVER04
SERVER05

也可以寫成:

$Servers = @("SERVER01", "SERVER02", "SERVER03")

兩種都可以。

如果資料比較多,我自己比較喜歡一行放一個:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

日後增加或刪除比較容易看。

Array 裡面有幾筆資料?

可以:

$Servers.Count

例如結果:

5

代表現在 Server 清單共有 5 台。

也可以取其中一筆。

PowerShell Array 從:

0

開始計算。

所以:

$Servers[0]

會得到:

SERVER01

而:

$Servers[1]

則是:

SERVER02

這點跟很多程式語言一樣:

第一筆 = 0
第二筆 = 1
第三筆 = 2
有了 Server 清單,下一步就是 foreach

現在我們已經有:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

但 PowerShell 還不知道我們要怎麼處理這些 Server。

這時候可以使用:

foreach

例如:

foreach ($Server in $Servers) {

Write-Host $Server

}

輸出:

SERVER01
SERVER02
SERVER03
foreach 可以怎麼理解?

我覺得不用先背語法。

直接把:

foreach ($Server in $Servers)

翻成中文就好:

把 $Servers 裡面的每一台 Server,一次拿一台出來,暫時叫做 $Server。

第一次:

$Server = SERVER01

第二次:

$Server = SERVER02

第三次:

$Server = SERVER03

一直做到整個 Array 處理完。

所以:

foreach ($Server in $Servers) {

Write-Host "Checking $Server"

}

結果:

Checking SERVER01
Checking SERVER02
Checking SERVER03

這就是我們從「單台處理」跨到「批次處理」最重要的一步。

第一個多台 Server 實作:確認網路是否可達

今天暫時不碰 PowerShell Remoting。

先做一件最簡單的事情:

確認 Server 是否能回應 ICMP Ping。

PowerShell 可以使用:

Test-Connection

例如:

Test-Connection -ComputerName SERVER01 -Count 1

它的概念和:

ping SERVER01

有點類似。

如果我們只想知道成功或失敗,可以:

Test-Connection -ComputerName SERVER01
-Count 1 `
-Quiet

成功可能回:

True

失敗:

False

這就非常適合拿來放進 Script。

加進 foreach

現在把 Server Array 和 Test-Connection 合在一起:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

foreach ($Server in $Servers) {

$Result = Test-Connection `
    -ComputerName $Server `
    -Count 1 `
    -Quiet

Write-Host "$Server : $Result"

}

假設結果:

SERVER01 : True
SERVER02 : True
SERVER03 : False

我們就完成了第一個:

多台 Server 批次檢查。

不過 True / False 不太適合拿給人看

如果最後報表長這樣:

SERVER01 True
SERVER02 True
SERVER03 False

自己看得懂。

但如果是給其他人看,我會比較希望是:

SERVER01 Online
SERVER02 Online
SERVER03 Offline

這時候可以加入:

if
加入第一個 if 判斷

例如:

if ($Result -eq $true) {

Write-Host "$Server is Online"

}
else {

Write-Host "$Server is Offline"

}

整段變成:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

foreach ($Server in $Servers) {

$Result = Test-Connection `
    -ComputerName $Server `
    -Count 1 `
    -Quiet

if ($Result -eq $true) {

    Write-Host "$Server is Online"

}
else {

    Write-Host "$Server is Offline"

}

}

結果:

SERVER01 is Online
SERVER02 is Online
SERVER03 is Offline

到這裡,程式已經開始可以根據不同情況做不同事情。

不要只把結果印在螢幕上

跟前幾天一樣,我們真正想要的是:

Server
Status
CheckTime

例如:

Server Status CheckTime
SERVER01 Online 2026/09/13 08:10
SERVER02 Online 2026/09/13 08:10
SERVER03 Offline 2026/09/13 08:10

所以我們需要把每一次檢查結果變成 Object。

使用 PSCustomObject 建立自己的資料

可以寫:

[PSCustomObject]@{
Server = $Server
Status = "Online"
CheckTime = Get-Date
}

它代表:

我要自己建立一個 PowerShell Object。

裡面有:

Server
Status
CheckTime

例如:

$Result = [PSCustomObject]@{
Server = "SERVER01"
Status = "Online"
CheckTime = Get-Date
}

輸入:

$Result

會得到類似:

Server Status CheckTime


SERVER01 Online 2026/09/13 08:10:30

這跟 Day 3 的 Object 概念又接起來了。

把每台 Server 的結果收集起來

我們先建立一個空 Array:

$Results = @()

接著每檢查完一台,就把結果放進去。

完整一點:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

$Results = @()

foreach ($Server in $Servers) {

$PingResult = Test-Connection `
    -ComputerName $Server `
    -Count 1 `
    -Quiet

if ($PingResult) {

    $Status = "Online"

}
else {

    $Status = "Offline"

}

$Result = [PSCustomObject]@{
    Server    = $Server
    Status    = $Status
    CheckTime = Get-Date
}

$Results += $Result

}

最後:

$Results

就會得到:

Server Status CheckTime


SERVER01 Online 2026/09/13 08:15:01
SERVER02 Online 2026/09/13 08:15:02
SERVER03 Offline 2026/09/13 08:15:06

這就是一份真正的批次檢查結果。

再把 Day 4 的 CSV 接回來

既然:

$Results

現在是一批 Object,就可以直接:

$Results |
Export-Csv -Path "C:\Temp\Server_Status.csv"
-NoTypeInformation `
-Encoding UTF8

整個流程就變成:

Server Array

foreach

Test-Connection

if

PSCustomObject

Results Array

Export-Csv

這已經開始有一點真正維運 Script 的樣子。

再把日期放進檔名

延續 Day 4:

$Date = Get-Date -Format "yyyyMMdd"

然後:

$ReportFile = "C:\Temp\Server_Status_$Date.csv"

例如:

C:\Temp\Server_Status_20260913.csv
今天的完整 Script

最後把今天所有東西整理在一起:

=====================================

Multi Server Connectivity Check

=====================================

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

$ReportFolder = "C:\Temp"
$Date = Get-Date -Format "yyyyMMdd"
$ReportFile = "$ReportFolder\Server_Status_$Date.csv"

確認報表資料夾

if (-not (Test-Path $ReportFolder)) {

New-Item `
    -Path $ReportFolder `
    -ItemType Directory

}

儲存檢查結果

$Results = @()

foreach ($Server in $Servers) {

Write-Host "Checking $Server..."

$PingResult = Test-Connection `
    -ComputerName $Server `
    -Count 1 `
    -Quiet

if ($PingResult) {

    $Status = "Online"

}
else {

    $Status = "Offline"

}

$Result = [PSCustomObject]@{
    Server    = $Server
    Status    = $Status
    CheckTime = Get-Date
}

$Results += $Result

}

顯示結果

$Results

輸出 CSV

$Results |
Export-Csv -Path $ReportFile
-NoTypeInformation `
-Encoding UTF8

Write-Host ""
Write-Host "Check completed."
Write-Host "Report: $ReportFile"
執行之後我們得到什麼?

PowerShell 畫面可能是:

Checking SERVER01...
Checking SERVER02...
Checking SERVER03...

Server Status CheckTime


SERVER01 Online 2026/09/13 08:20:01
SERVER02 Online 2026/09/13 08:20:02
SERVER03 Offline 2026/09/13 08:20:06

Check completed.
Report: C:\Temp\Server_Status_20260913.csv

同時會產生:

C:\Temp\Server_Status_20260913.csv

到這裡,我們已經完成了第一支真正有:

多台設備
+
條件判斷
+
批次處理
+
報表

的 PowerShell Script。

不過 Ping 不通,不一定代表 Server 掛了

這裡一定要補一個維運上的觀念。

如果:

Test-Connection

回傳:

False

不能直接下結論說 Server 已經 Down。

因為可能是:

ICMP 被 Firewall 擋掉
DNS 解析失敗
網路 ACL 阻擋
跨 VLAN 路由問題
主機真的離線

所以這份報表比較精確的說法應該是:

ICMP Connectivity Check

而不是:

Server Health Check

真正的 Server Health Check 之後還需要加入:

TCP Port
Service
Disk
CPU
Memory
Event Log

甚至從多個角度一起判斷。

這也是系統維運很重要的一件事:

一個檢查結果,通常只代表一個觀察點,不代表整台系統的完整狀態。

Server 清單也不用永遠寫死在 Script 裡

目前我們使用:

$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)

如果只有幾台沒有問題。

但如果公司有:

50 台
100 台
300 台 Server

把所有 Hostname 寫進 Script 就不太方便。

我們前一天已經學過:

Import-Csv

所以未來可以準備:

Servers.csv

內容:

Server
SERVER01
SERVER02
SERVER03

然後:

$Servers = Import-Csv "C:\Temp\Servers.csv"

再:

foreach ($Server in $Servers) {

$Server.Server

}

這樣就開始把:

設定資料

和:

程式邏輯

分開。

以後增加 Server,只修改 CSV,不需要一直改 Script。

這會是後面做批次維運時很重要的做法。

foreach 跟 ForEach-Object 有什麼差別?

Day 3 我們碰過:

ForEach-Object

今天又看到:

foreach

兩個都可以逐筆處理資料。

例如 Pipeline 寫法:

$Servers |
ForEach-Object {

Write-Host "Checking $_"

}

也可以寫:

foreach ($Server in $Servers) {

Write-Host "Checking $Server"

}

目前這個系列裡,我會先優先使用第二種:

foreach

因為當程式開始加入:

if
Test-Connection
變數
多個步驟
錯誤處理

時,foreach 通常比較容易閱讀。

等後面對 Pipeline 熟悉之後,再依情況選擇就好。

今天最重要的不是把 foreach 背下來

今天第一次開始有一點「寫程式」的感覺。

我們碰到了:

$Variable
@()
foreach
if
[PSCustomObject]

但這些語法並不是今天最重要的事情。

真正重要的是思考方式已經開始從:

我要怎麼檢查 SERVER01?

變成:

我能不能定義一份 Server 清單,
然後讓同一套邏輯自動處理所有 Server?

這個差別很大。

因為維運自動化真正需要解決的不是:

怎麼讓一個指令變快?

而是:

怎麼讓原本只能處理一台設備的方法,可以重複套用到很多台設備。

Day 5 小結

今天我們第一次把前四天學到的東西真正串起來:

變數

Array

foreach

Test-Connection

if

PSCustomObject

Array

Export-Csv

從:

Test-Connection SERVER01

變成:

SERVER01
SERVER02
SERVER03
SERVER04
SERVER05

自動逐台檢查

統一整理結果

產生 CSV

這也是我們第一次真正跨入:

批次維運。


上一篇
Day 4|把 PowerShell 查詢結果變成 CSV:開始做第一份維運報表
下一篇
Day 6|PowerShell 錯誤處理:Script 出錯時,不要讓它直接停下來
系列文
系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言